package com.amumu.drama.common.config.redis;


import cn.hutool.core.thread.ThreadUtil;
import com.amumu.drama.common.base.annonation.OnJvmShutdown;
import com.amumu.drama.common.config.redis.ops.*;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.exceptions.JedisConnectionException;

import java.io.Closeable;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * Jedis命令使用模板
 * <p>
 * 根据http://redisdoc.com对redis操作的分类细分具体操作类。
 * 其实官网也是这样分类的https://redis.io/commands#generic
 * <p>
 * 与spring-data-redis主要不同的是，本模板更接近redis的原生操作。
 * <p>
 * 仅支持基本redis命令
 * 更新支持至Redis 3.0
 * <p>
 * Redis命令实现总共有
 * JedisCommands,
 * MultiKeyCommands,
 * AdvancedJedisCommands,
 * ScriptingCommands,
 * BasicCommands,
 * ClusterCommands,
 * SentinelCommands,
 * BinaryJedisCommands,
 * MultiKeyBinaryCommands,
 * AdvancedBinaryJedisCommands,
 * BinaryScriptingCommands
 * <p>
 * 本模板只实现了JedisCommands/MultiKeyCommands/ScriptingCommands
 * </pre>
 *
 * @author freethinker
 * @version jedis_3.1.0
 * @see <a href="http://redisdoc.com/">http://redisdoc.com/</a>
 */
public class JedisTemplate implements Closeable {

    /** 最大获取连接尝试数 */
    private static final int RETRY_COUNT_LIMIT = 5;
    /** Jedis客户端的唯一ID */
    protected final String id = UUID.randomUUID().toString();
    /** 单机实例缓存：JedisRateLimiterConfig -> JedisRateLimiter */
    private final ConcurrentHashMap<JedisRateLimiterConfig, JedisRateLimiter> config2JedisRateLimiterMap = new ConcurrentHashMap<>();
    /** 单机实例缓存：JedisRateLimiterConfig -> JedisConcurrentRequestLimiter */
    private final ConcurrentHashMap<JedisRateLimiterConfig, JedisConcurrentRequestLimiter> config2JedisConcurrentRequestLimiter = new ConcurrentHashMap<>();
    private final JedisOpsKey key = new JedisOpsKey(this);
    private final JedisOpsString string = new JedisOpsString(this);
    private final JedisOpsHash hash = new JedisOpsHash(this);
    private final JedisOpsList list = new JedisOpsList(this);
    private final JedisOpsSet set = new JedisOpsSet(this);
    private final JedisOpsSortedSet sortedSet = new JedisOpsSortedSet(this);
    private final JedisOpsHyperLogLog hyperLogLog = new JedisOpsHyperLogLog(this);
    private final JedisOpsGeo geo = new JedisOpsGeo(this);
    private final JedisOpsPubsub pubsub = new JedisOpsPubsub(this);
    private final JedisOpsTransaction transaction = new JedisOpsTransaction(this);
    private final JedisOpsConfig config = new JedisOpsConfig(this);
    private final JedisOpsScript script = new JedisOpsScript(this);
    private final JedisOpsServer server = new JedisOpsServer(this);
    /** Jedis连接池 */
    protected JedisPool jedisPool;

    public JedisTemplate(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    /**
     * 带重试获取Jedis连接,提供完整的jedis功能。
     * 需要使用 try with resource 来进行自动归还连接池，例如：
     * try(Jedis jedis=jedisTemplate.getJedis()){
     * // do something.
     * }
     * 建议使用 {@link #(JedisRunnable)}
     */
    @Deprecated
    public Jedis getJedis() {
        Jedis jedis = null;
        JedisConnectionException ex = null;
        int tryTimes = 1;
        do {
            try {
                jedis = jedisPool.getResource();
            } catch (JedisConnectionException e) {
                ex = e;
            }
            if (jedis == null) {
                // 休息几毫秒再尝试
                ThreadUtil.sleep(50 * tryTimes);
            }
        } while (jedis == null && tryTimes++ <= RETRY_COUNT_LIMIT);
        if (null == jedis && null != ex) {
            throw ex;
        }
        return jedis;
    }

    /**
     * 带自动重试获取jedis连接的执行(不需要使用try-with-resource)
     * <p>
     * 用法
     * <pre>
     *       jedisTemplate.execute(jedis -> {
     *          jedis.set("aa","bb");
     *       });
     * </pre>
     */
    public void execute(Consumer<Jedis> consumer) {
        try (Jedis jedis = getJedis()) {
            consumer.accept(jedis);
        }
    }

    /**
     * 带自动重试获取jedis连接的执行(不需要使用try-with-resource)
     * <p>
     * 用法
     * <pre>
     *       String result = jedisTemplate.execute(jedis -> {
     *                           return jedis.get("aa");
     *                       });
     * </pre>
     */
    public <V> V execute(final JedisCallable<V> callable) {
        try (Jedis jedis = getJedis()) {
            return callable.call(jedis);
        }
    }

    /** 批量操作提供性能 */
    public void pipeline(Consumer<Pipeline> handlePipeline) {
        try (Jedis jedis = getJedis()) {
            Pipeline pipelined = jedis.pipelined();
            handlePipeline.accept(pipelined);
            pipelined.sync();
        }
    }

    /**
     * 获取可重入锁
     * <p>
     * jedis的分布式可重入锁JedisLock Java对象实现了java.ut2il.concurrent.locks.Lock接口，同时还支持自动过期解锁。
     * <p>
     * JedisLock lock = jedisTemplate.getLock("anyLock");
     * // 最常见的使用方法
     * lock.lock();
     * <p>
     * // 支持过期解锁功能
     * // 10秒钟以后自动解锁
     * // 无需调用unlock方法手动解锁
     * lock.lock(10, TimeUnit.SECONDS);
     * <p>
     * // 尝试加锁，最多等待100秒，上锁以后10秒自动解锁
     * boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
     * ...
     * lock.unlock();
     */
    @Deprecated
    public JedisLock getLock(Object obj) {
        return new JedisLock(String.valueOf(obj), this);
    }

    /**
     * 获取可重入锁,用try with resource自动释放锁,
     * <p>
     * 例如：
     * try(JedisLock lock = jedisTemplate.lock("anyLock")) {
     * ...
     * }
     */
    public JedisLock lock(Object obj) {
        JedisLock lock = new JedisLock(String.valueOf(obj), this);
        lock.lock();
        return lock;
    }

    /**
     * 获取可重入锁,用try with resource自动释放锁,
     * <p>
     * 例如：
     * try(JedisLock lock = jedisTemplate.lock("anyLock")) {
     * ...
     * }
     */
    public JedisLock lock(Object obj, long timeoutMs) {
        JedisLock lock = new JedisLock(String.valueOf(obj), this);
        lock.lock(timeoutMs, TimeUnit.MILLISECONDS);
        return lock;
    }

    /**
     * 基于令牌桶算法的限流器
     * <p>
     * 例如针对指定ip限流5分钟内请求10次：
     * <p>
     * JedisRateLimiterConfig config=new JedisRateLimiterConfig("ipratelimit",10,5*60);
     * if(jedisTemplate.rateLimit(config).tryAcquire(ip)){
     * // 获得请求令牌
     * }
     *
     * @param config 限流配置
     * @return true:允许请求，false:超出频率限制
     */
    public JedisRateLimiter rateLimit(JedisRateLimiterConfig config) {
        JedisRateLimiter jedisRateLimiter = config2JedisRateLimiterMap.get(config);
        if (jedisRateLimiter == null) {
            config2JedisRateLimiterMap.putIfAbsent(config, new JedisRateLimiter(config, this));
            jedisRateLimiter = config2JedisRateLimiterMap.get(config);
        }
        return jedisRateLimiter;
    }

    /**
     * 高并发请求限制器
     * <p>
     * 例如网页访问，每次请求有唯一的traceId，限定在5分钟内只能有10个请求
     * <p>
     * JedisConcurrentRequestLimiterConfig config=new JedisConcurrentRequestLimiterConfig("traceIdLimit",10，5*60);
     * if(jedisTemplate.concurrentRequestLimit(config).tryAcquire(traceId)){
     * // 获得请求
     * }
     *
     * @param config 限流配置
     */
    public JedisConcurrentRequestLimiter concurrentRequestLimit(JedisRateLimiterConfig config) {
        JedisConcurrentRequestLimiter jedisConcurrentRequestLimiter = config2JedisConcurrentRequestLimiter.get(config);
        if (jedisConcurrentRequestLimiter == null) {
            config2JedisConcurrentRequestLimiter.putIfAbsent(config, new JedisConcurrentRequestLimiter(config, this));
            jedisConcurrentRequestLimiter = config2JedisConcurrentRequestLimiter.get(config);
        }
        return jedisConcurrentRequestLimiter;
    }

    public JedisOpsKey key() {
        return key;
    }

    public JedisOpsString string() {
        return string;
    }

    public JedisOpsHash hash() {
        return hash;
    }

    public JedisOpsList list() {
        return list;
    }

    public JedisOpsSet set() {
        return set;
    }

    public JedisOpsSortedSet sortedSet() {
        return sortedSet;
    }

    public JedisOpsHyperLogLog hyperLogLog() {
        return hyperLogLog;
    }

    public JedisOpsGeo geo() {
        return geo;
    }

    public JedisOpsPubsub pubsub() {
        return pubsub;
    }

    public JedisOpsTransaction transaction() {
        return transaction;
    }

    public JedisOpsConfig config() {
        return config;
    }

    public JedisOpsScript script() {
        return script;
    }

    public JedisOpsServer server() {
        return server;
    }

    /**
     * Closes this stream and releases any system resources associated
     * with it. If the stream is already closed then invoking this
     * method has no effect.
     *
     * <p> As noted in {@link AutoCloseable#close()}, cases where the
     * close may fail require careful attention. It is strongly advised
     * to relinquish the underlying resources and to internally
     * <em>mark</em> the {@code Closeable} as closed, prior to throwing
     * the {@code IOException}.
     *
     * @throws IOException if an I/O error occurs
     */
    @Override
    @OnJvmShutdown
    public void close() throws IOException {
        this.jedisPool.close();
    }

    /**
     * jedis带返回值的执行块
     */
    public interface JedisCallable<V> {

        V call(Jedis jedis);
    }

    /**
     * jedis执行块
     */
    public interface JedisRunnable {

        void run(Jedis jedis);
    }
}
